#include "icmp.h"


#define ICMP_OK 0							/*!< Codigo de OK*/
#define ICMP_ERR -1							/*!< Codigo de ERROR*/

#define ICMP_HEADER_LEN 4					/*!< Tamanno de la cabecera (Sin parametros)*/
#define ICMP_PARAMETERS_LEN 4				/*!< Tamanno de los parametros*/
#define ICMP_CHECKSUM_LEN 2					/*!< Tamanno del checksum*/
#define ICMP_CHECKSUM_POS 2					/*!< Posicion del checksum*/

#define ICMP_PROTOCOL_NUMBER (0x01)			/*!< Identificador IP del protocolo ICMP*/

#define ICMP_ECHO_REQ_TYPE (0x08)			/*!< Tipo ICMP de ECHO REQUEST*/
#define ICMP_ECHO_REP_TYPE (0x00)			/*!< Tipo ICMP de ECHO REPLY*/
#define ICMP_ECHO_CODE (0x00)				/*!< Codigo ICMP de ECHO*/

#define getEchoDataSize(size) ((size)-ICMP_HEADER_LEN-ICMP_PARAMETERS_LEN)		/*Retorna el tamanno de los datos en un datagrama ICMP*/
#define getEchoParametersPointer(data) ((data)+ICMP_HEADER_LEN)					/*Retorna un puntero a los parametros de un datagrama ICMP*/
#define getEchoData(data) ((data)+ICMP_HEADER_LEN+ICMP_PARAMETERS_LEN)			/*Retorna un puntero a los datos de un datagrama ICMP*/

typedef enum {ICMP_ECHO_REP=0, ICMP_ECHO_REQ=8, ICMP_UNDEF=-1} ICMP_TYPE;		/*!< Tipos ICMP*/

short icmp_inicialized=0;		/*!< Protocolo ICMP incializado*/

CALLBACK_MEM callbackArray=NULL;		/*!< Tabla de registro de funciones de callback*/

static BYTE ICMP_CHECKSUM_BASE_VALUE[]={0x00, 0x00};		/*!< Valor base del campo checksum (Antes de calcularse el checksum real)*/
static BYTE ICMP_CORRECT_CHECKSUM[]={0x00, 0x00};			/*!< Valor correcto de checksum en recepcion*/


static BYTE local_dir_ip_origen[IP_ALEN];
static BYTE * local_data = NULL;
static unsigned int local_size;
static op_ip local_opciones;

/**
* @brief Funcion de notificacion desde IP para ICMP.
*
* @param dir_ip_origen Direccion IP del datagrama entrante.
* @param tamano Tamanno total del datagrama recibido
* @param datos Datagrama recibido
* @param opciones Opciones IP del datagrama recibido
*/
int icmpNotify(BYTE *dir_ip_origen, unsigned tamano, BYTE *datos, op_ip *opciones);

/**
* @brief Crea un datagrama ICMP segun el protocolo RFC 1700.
*
* Nota.: Esta funcion alloca memoria dinamicamente, que debera ser liberada
* por el cliente de forma externa con free.
*
* @param type Tipo de datagrama ICMP.
* @param code Codigo del datagrama ICMP.
* @param parameters Parametros del datagrama ICMP
* @param data Datos del datagrama ICMP
* @param size Tamanno de los datos del datagrama ICMP
* @param totalSize Puntero a entero en el que se almaceanara el tamano final del datagrama ICMP.
* @return Datagrama ICMP creado, NULL si error.
*/
BYTE *createICMPDatagram(BYTE type, BYTE code, BYTE *parameters, BYTE *data, unsigned int size, unsigned int *totalSize);

/**
* @brief Comprueba si el checksum del datagrama data es correcto.
*
* Ejecuta la suma de comprobacion sobre todo el datagrama (Incluida la cabecera),
* y comprueba que concuerda con el valor esperado, 0.
*
* @param data Datagrama a checkear.
* @param size Tamanno del datagrama a checkear.
*/
short isCorrectChecksum(BYTE *data, int size);

/**
* @brief Retorna el tipo de datagrama ICMP
*
* @param data Datagrama ICMP a analizar.
* @return Tipo del datagrama, UNDEF si irreconocible.
*/
ICMP_TYPE getICMPType(BYTE *data);

/**
* @brief Crea el campo "Parametros" de ICMP y lo vuelca sobre parameters.
*
* Nota.: Es responsibilidad del cliente reservar memoria adecuada en parameters
* para albergar los parametros creados.
*
* @param id Identificacion del proceso.
* @param seq Numero de secuencia.
* @param parameters Array de bytes sobre el que se volcaran los parametros.
* @return 0 si correcto, -1 si error.
*/
int createEchoParametersICMP(unsigned int id, unsigned int seq, BYTE *parameters);

/**
* @brief Extrae los parametros del campo parameters, y los vuelca sobre id y seq.
*
* @param parameters Array de parametros sobre el que ejecutar la extraccion.
* @param id Puntero a entero sin signo sobre el que se volcaran los datos de id.
* @param seq Puntero a entero sin signo sobre el que se volcaran los datos de seq.
* @return 0 si correcto, -1 si error.
*/
int getEchoParametersICMP(BYTE *parameters, unsigned int *id, unsigned int *seq);

/**
* @brief Procesa un datagrama ICMP de ECHO REPLY. Notifica al cliente de ICMP
* la recepcion del ECHO REPLY.
*
* @param dir_ip_origen Direccion IP del remitente.
* @param data Datagrama ICMP recibido.
* @param size Tamanno total del datagrama ICMP recibido.
* @param opcions Opciones IP recibidas.
* @return 0 si correcto, -1 si error.
*/
int processEchoRepICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions);

/**
* @brief Procesa un datagrama ICMP de ECHO REQUEST. Envia un datagrama ICMP
* de ECHO REPLY al remitente en respuesta a la peticion.
*
* @param dir_ip_origen Direccion IP del remitente.
* @param data Datagrama ICMP recibido.
* @param size Tamanno total del datagrama ICMP recibido.
* @param opcions Opciones IP recibidas.
* @return 0 si correcto, -1 si error.
*/
int processEchoReqICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions);

/**
* @brief Registra una funcion de callback.
*
* @param id Identificador asociado a la funcion de callback.
* @param callback Funcion de callback a registrar.
* @return 0 si correcto, -1 si error.
*/
int registerEchoCallbackICMP(unsigned int id, pf_notificacion_echo callback);

/**
* @brief Extrae una funcion de callback.
*
* @param id Identificador asociado a la funcion de callback a extraer.
* @return Funcion de callback solicitada, NULL si no se ha hallado.
*/
pf_notificacion_echo getEchoCallbackICMP(unsigned int id);


int icmpNotify(BYTE *dir_ip_origen, unsigned tamano, BYTE *datos, op_ip *opciones)
{
	ICMP_TYPE type;
	
	if(dir_ip_origen==NULL || (tamano>0 && datos==NULL) || tamano<ICMP_HEADER_LEN){
		return -1;
	}
	
	if(!isCorrectChecksum(datos, tamano)){			/*Comprobacion de checksum*/
		return -1;
	}
	
	type=getICMPType(datos);
	switch(type)
	{
		case ICMP_ECHO_REQ:				/*ECHO REQUEST*/
			return processEchoReqICMP(dir_ip_origen, datos, tamano, opciones);

		case ICMP_ECHO_REP:				/*ECHO REPLY*/
			return processEchoRepICMP(dir_ip_origen, datos, tamano, opciones);
		
		default:						/*TIPO DESCONOCIDO (o no implementado)*/
			return -1;
	}
	
	return -1;
}

int icmp_inicializa()
{
	if(icmp_inicialized){		/*Protocolo ICMP ya inicializado*/
		return ICMP_OK;
	}
	
	if(ip_inicializa()!=0){		/*Inicializacion de protocolo IP*/
		return ICMP_ERR;
	}
	
	if(ip_registra_protocolo(ICMP_PROTOCOL_NUMBER, icmpNotify)!=0){		/*Registro de la funcion de callback ICMP en IP*/
		return ICMP_ERR;
	}
	
	callbackArray=createCallbackMem();		/*Creacion del la tabla de registros de callback*/
	if(callbackArray==NULL){
		return ICMP_ERR;
	}
	
	icmp_inicialized=1;
	
	return ICMP_OK;
}

int icmp_finaliza()
{
	freeCallbackMem(callbackArray);
	
	icmp_inicialized=0;
	
	return ICMP_OK;
}

int icmp_envia_echo(BYTE *dir_ip_destino, unsigned int id,
  unsigned int seq, pf_notificacion_echo callback,
  unsigned int tamano, BYTE *datos, op_ip *opciones)
{
	BYTE parameters[ICMP_PARAMETERS_LEN];
	
	if(dir_ip_destino==NULL || (tamano>0 && datos==NULL)){
		return ICMP_ERR;
	}
	
	if(registerEchoCallbackICMP(id, callback)!=ICMP_OK){		/*Registro de la funcion de callback*/
		return ICMP_ERR;
	}
	
	if(createEchoParametersICMP(id, seq, parameters)!=ICMP_OK){		/*Creacion de parametros de ECHO*/
		return ICMP_ERR;
	}
	
	/*Envio por ICMP del datagrama*/
	if(icmp_envia_datagrama(dir_ip_destino, ICMP_ECHO_REQ_TYPE, ICMP_ECHO_CODE, parameters, tamano, datos, opciones)!=ICMP_OK){
		return ICMP_ERR;
	}
	
	return ICMP_OK; 
}


int icmp_envia_datagrama(BYTE *dir_ip_destino, BYTE tipo, BYTE codigo,
  BYTE *parametros, unsigned int tamano, BYTE *datos, op_ip *opciones)
{
	BYTE *icmp_datagram;	
	unsigned int tamanoTotal;
	
	if(dir_ip_destino==NULL || (tamano>0 && datos==NULL)){
		return ICMP_ERR;
	}
	
	/*Creacion del datagrama ICMP*/
	icmp_datagram=createICMPDatagram(tipo, codigo, parametros, datos, tamano, &tamanoTotal);
	if(icmp_datagram==NULL){
		return ICMP_ERR;
	}
	
	/*Envio IP del datagrama*/
	if(ip_envia_datagrama(ICMP_PROTOCOL_NUMBER, dir_ip_destino, tamanoTotal, icmp_datagram, opciones)!=0){
		free(icmp_datagram);
		return ICMP_ERR;
	}
	
	free(icmp_datagram);
	
	return ICMP_OK;
}


/**********************************************************************************************************************
    											FORMATO DE DATAGRAMA ICMP
    											
       _______________________________________________________________________________________________________
      |                         |                         |                                                   |      
      |         TIPO            |         CODIGO          |                     CHECKSUM                      |
      |_________________________|_________________________|___________________________________________________|
      |                                                                                                       |
      |                                              PARAMETROS                                               |
      |_______________________________________________________________________________________________________|
      |                                                                                                       |
      |                                                 DATOS                                                 |
      |                                                   .                                                   |
      |                                                   .                                                   |
      |                                                   .                                                   |
      |_______________________________________________________________________________________________________|

**********************************************************************************************************************/
BYTE *createICMPDatagram(BYTE type, BYTE code, BYTE *parameters, BYTE *data, unsigned int size, unsigned int *totalSize)
{
	BYTE *datagram=NULL;
	int offset=0;
	
	if(parameters!=NULL){		/*Datagrama con parametros*/
		*totalSize=ICMP_HEADER_LEN+ICMP_PARAMETERS_LEN+size;
	}else{						/*Datagrama sin parametros*/
		*totalSize=ICMP_HEADER_LEN+size;
	}
	
	datagram=(BYTE *)calloc(*totalSize, sizeof(BYTE));
	if(datagram==NULL){
		return NULL;
	}
	
	datagram[offset++]=type;												/*TIPO (1 Byte)*/
	datagram[offset++]=code;												/*CODIGO (1 Byte)*/
	memcpy(datagram+offset, ICMP_CHECKSUM_BASE_VALUE, ICMP_CHECKSUM_LEN);	/*CHECKSUM (2 Bytes)*/
	offset+=ICMP_CHECKSUM_LEN;
		
	if(parameters!=NULL){													/*PARAMETROS (ICMP_PARAMETERS_LEN Bytes)*/
		memcpy(datagram+offset, parameters, ICMP_PARAMETERS_LEN);
		offset+=ICMP_PARAMETERS_LEN;
	}
	
	if(data!=NULL){															/*DATOS (Tamanno variable)*/
		memcpy(datagram+offset, data, size);
	}
	
	calcula_checksum_ip(*totalSize, datagram, datagram+ICMP_CHECKSUM_POS);

	return datagram;
}

short isCorrectChecksum(BYTE *data, int size)
{
	BYTE checksum[ICMP_CHECKSUM_LEN];
	
	calcula_checksum_ip(size, data, checksum);
	
	if(memcmp(checksum, ICMP_CORRECT_CHECKSUM, ICMP_CHECKSUM_LEN)!=0){	/*Comparacion de checksum con el valor esperado*/
		return 0;
	}
	
	return 1;
}

ICMP_TYPE getICMPType(BYTE *data)
{
	if(data==NULL){
		return ICMP_UNDEF;
	}
	
	if(data[0]==ICMP_ECHO_REP_TYPE && data[1]==ICMP_ECHO_CODE){
		return ICMP_ECHO_REP;
	}
	else if(data[0]==ICMP_ECHO_REQ && data[1]==ICMP_ECHO_CODE){
		return ICMP_ECHO_REQ;
	}
	
	return ICMP_UNDEF;
}


int createEchoParametersICMP(unsigned int id, unsigned int seq, BYTE *parameters)
{
	uint16_t *uintParameters;
	
	if(parameters==NULL){
		return ICMP_ERR;
	}
	
	uintParameters=(uint16_t *) parameters;
	
	uintParameters[0]=htons(id);		/*Identificador (Endianess de red)*/
	uintParameters[1]=htons(seq);		/*Numero de secuencia (Endianess de red)*/
	
	return ICMP_OK;
}

int getEchoParametersICMP(BYTE *parameters, unsigned int *id, unsigned int *seq)
{
	uint16_t *uintParameters;
	
	uintParameters=(uint16_t *) parameters;
	
	*id=ntohs(uintParameters[0]);		/*Identificador (Endianess de host)*/
	*seq=ntohs(uintParameters[1]);		/*Numero de secuencia (Endianess de host)*/
	
	return ICMP_OK;
}

int processEchoRepICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions)
{
	unsigned int id, seq;
	pf_notificacion_echo callbackFun;
	
	if(getEchoParametersICMP(getEchoParametersPointer(data), &id, &seq)!=ICMP_OK){
		return ICMP_ERR;
	}
	
	/*Extraccion de la funcion de notificacion*/
	callbackFun=getEchoCallbackICMP(id);
	if(callbackFun==NULL){
		return ICMP_ERR;
	}
	
	/*Llamada a la funcion de notificacion de ECHO REPLY*/
	return callbackFun(dir_ip_origen, seq, getEchoDataSize(size), getEchoParametersPointer(data), opcions);
}

void delayedProcessEchoReqICMP(int signal)
{
	icmp_envia_datagrama(local_dir_ip_origen, ICMP_ECHO_REP_TYPE, ICMP_ECHO_CODE, getEchoParametersPointer(local_data),
				getEchoDataSize(local_size), getEchoData(local_data), &local_opciones);
	free(local_data);
}

int processEchoReqICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions)
{
    struct itimerval curr;
    
	curr.it_interval.tv_sec = 0;
	curr.it_interval.tv_usec = 0;
	
	curr.it_value.tv_sec = 0;
	curr.it_value.tv_usec = 1000;

	memcpy(local_dir_ip_origen, dir_ip_origen, ETH_ALEN);
	local_data = malloc(size);
	memcpy(local_data, data, size);
	memcpy(&local_opciones, opcions, sizeof(op_ip));
	
	local_size = size;

	if(signal(SIGALRM, delayedProcessEchoReqICMP)==SIG_ERR){
		return ICMP_ERR;
	}
	if(setitimer(ITIMER_REAL, &curr, NULL)==-1){
		return ICMP_ERR;
	}
	
	return ICMP_OK;
}

int registerEchoCallbackICMP(unsigned int id, pf_notificacion_echo callback)
{
	/*Registro del callback en la tabla de registros*/
	if(registerCallback(callbackArray, id, callback)==CALLBACK_MEM_ERR){
		return ICMP_ERR;
	}
	
	return ICMP_OK;

}

pf_notificacion_echo getEchoCallbackICMP(unsigned int id)
{
	return getCallback(callbackArray, id);
}

